#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "glm/glm.hpp"
#include "glm/gtc/type_precision.hpp"

inline glm::vec2 ComplexMul(const glm::vec2 &a, const glm::vec2 &b) {
  return glm::vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}
inline glm::vec2 ComplexSq(const glm::vec2 &a) {
  return glm::vec2(a.x * a.x - a.y * a.y, 2.0f * a.x * a.y);
}
float ComputeDistance(const glm::vec2 &c, int bailout) {
  glm::vec2 z = glm::vec2(0.0f, 0.0f);
  glm::vec2 dz = glm::vec2(1.0f, 0.0f);
  for (int i = 0; i < bailout; ++i) {
    dz = 2.0f * ComplexMul(z, dz) + glm::vec2(1.0f, 0.0f);
    z = ComplexSq(z) + c;
    const float m2 = glm::dot(z, z);
    if (m2 > 100.0f) {
      return glm::sqrt(m2 / glm::dot(dz, dz)) * 0.5f * glm::log(m2);
    }
  }
  return 0.0f;
}
int main(int argc, char **argv) {
  glm::vec2 center = glm::vec2(-0.761574f, -0.0847596f);
  float zoom = 1000.0f;
  int bailout = 1000;
  int size = 800;

  for (int i = 0; i < argc; ++i) {
    if (std::strcmp(argv[i], "-centerx") == 0) {
      center.x = std::atof(argv[++i]);
    } else if (std::strcmp(argv[i], "-centery") == 0) {
      center.y = std::atof(argv[++i]);
    } else if (std::strcmp(argv[i], "-zoom") == 0) {
      zoom = std::atof(argv[++i]);
    } else if (std::strcmp(argv[i], "-bailout") == 0) {
      bailout = std::atoi(argv[++i]);
    } else if (std::strcmp(argv[i], "-size") == 0) {
      size = std::atoi(argv[++i]);
    }
  }
  std::printf("centerx = %f\n", center.x);
  std::printf("centery = %f\n", center.y);
  std::printf("zoom    = %f\n", zoom);
  std::printf("bailout = %d\n", bailout);
  std::printf("size    = %d\n", size);

  std::vector<glm::u8vec4> image(size * size);

  for (int y = 0; y < size; ++y) {
    for (int x = 0; x < size; ++x) {
      const int idx = x + y * size;
      glm::vec2 c = glm::vec2(2.0f * (x / static_cast<float>(size) - 0.5f),
                              2.0f * (y / static_cast<float>(size) - 0.5f));
      c /= zoom;
      c += center;
      float d = 1.1f * glm::pow(ComputeDistance(c, bailout) * zoom, 0.25f);
      image[idx].b = static_cast<glm::u8>(255.0f * d);
      image[idx].g = static_cast<glm::u8>(255.0f * d);
      image[idx].r = static_cast<glm::u8>(255.0f * d);
      image[idx].a = 255;
    }
  }

  FILE *fp = std::fopen("mandelbrot.tga", "wb");
  const glm::u8 tga_header[18] = {
    0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    size & 0x00ff, (size & 0xff00) >> 8,
    size & 0x00ff, (size & 0xff00) >> 8,
    32, 0
  };
  std::fwrite(tga_header, sizeof(tga_header), 1, fp);
  for (int y = 0; y < size; ++y) {
    for (int x = 0; x < size; ++x) {
      if (x < 2 || y < 2 || x >= size - 2 || y >= size - 2) {
        // white frame
        std::fwrite(&glm::u8vec4(255)[0], sizeof(glm::u8vec4), 1, fp);
      } else {
        const int i = x + y * size;
        std::fwrite(&image[i], sizeof(glm::u8vec4), 1, fp);
      }
    }
  }
  fclose(fp);
  return EXIT_SUCCESS;
}
